package com.panorama.visitor;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.List;

import com.panorama.visitor.annotation.Copy;

public class CopyVisitor extends AbstractReflectionVisitor<VisitableNode> {
	private VisitableNode node = null;
	private Field currentField = null;
	
	@Override
	public Visitor<VisitableNode> enter(VisitableNode node) {
		return new CopyVisitor();
	}
	
	@Override
	public void visit(VisitableNode node) {
		this.node = newNode(node);
		
		if(this.node == null) {
			throw new Error("Cannot construct a new copy of node: " + node);
		}
	}
	
	@Override
	public VisitableNode leave(VisitableNode node) {
		return this.node;
	}
	
	@Override
	protected void visitField(VisitableNode node, Field field) {
		currentField = field;
		
		super.visitField(node, field);
	}
	
	@Override
	public void collect(List<VisitableNode> values) {
		set(this.node, currentField, newChildCollection(values));
	}
	
	@Override
	public void collect(VisitableNode value) {
		set(this.node, currentField, value);
	}
	
	public VisitableNode newNode(VisitableNode node) {
		VisitableNode newNode = null;
		
		Constructor<?> constructor = null;
		
		try {
			constructor = node.getClass().getConstructor();
		} catch (SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		if(constructor == null) {
			// TODO
			throw new Error("Cannot construct a new copy of node: " + node);
		}
		
		try {
			newNode = (VisitableNode) constructor.newInstance();
		} catch (IllegalArgumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InstantiationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		if(newNode == null) {
			throw new Error("Cannot construct a new copy of node: " + node);
		}
		
		copyNode(node, newNode);
		
		return newNode;
	}
	
	public void copyNode(VisitableNode node, VisitableNode newNode) {
		Class<?> clazz = node.getClass();
		
		do {
			for(Field field: clazz.getDeclaredFields()) {
				if(field.isAnnotationPresent(Copy.class)) {
					set(newNode, field, get(node, field));
				}
			}
		} while((clazz = clazz.getSuperclass()) != null);
	}
	
	public void set(Object bean, Field field, Object value) {
		Method set = null;
		
		try {
			set = bean.getClass().getMethod(getSetter(field.getName()), field.getType());
			
			if(set == null) {
				throw new Error("Could not get setter method for node: " + this.node);
			}
			
			set.invoke(bean, value);
		} catch (SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	private String getSetter(String name) {
		return "set" + Character.toUpperCase(name.charAt(0)) + name.substring(1);
	}
	
	@SuppressWarnings("unchecked")
	private Collection<? extends VisitableNode> newChildCollection(Collection<? extends VisitableNode> childCollection) {
		Collection<? extends VisitableNode> newChildCollection = null;
		
		Constructor<?> constructor = null;
		
		try {
			constructor = childCollection.getClass().getConstructor(Collection.class);
		} catch (SecurityException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (NoSuchMethodException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		if(constructor == null) {
			throw new Error("Could not construct a new child collection.");
		}
		
		try {
			newChildCollection = (Collection<? extends VisitableNode>) constructor.newInstance(childCollection);
		} catch (IllegalArgumentException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InstantiationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (InvocationTargetException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		if(newChildCollection == null) {
			throw new Error("Could not construct a new child collection.");
		}
		
		return newChildCollection;
	}
}
